package game;

import com.sun.j3d.loaders.Scene;
import com.sun.j3d.utils.universe.SimpleUniverse;

import javax.media.j3d.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.WindowConstants;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3f;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

public class App {
    private final static int UPDATE_RATE = 60;

    public static enum State {
        SCRATCH,
        PLAY,
        MOTION
    };

    public static State state;
    public static CueBall cueBall;
    public static CueStick cueStick;
    public static Table table;

    private static Timer timer;

    public static Canvas3D canvas3D;

    private static BranchGroup scene;
    private static TransformGroup TG;
    private static JFrame frame;
    private static int score = 0;
    private static JLabel scoreLabel;

    public static boolean isScratched = false;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new App();
            }
        });
    }

    public static void score() {
        score += 1;
        scoreLabel.setText("Score: " + String.valueOf(score));
    }

    public static void resetScore() {
        score = 0;
        scoreLabel.setText("Score: " + String.valueOf(score));
    }

    public App() {
        createScene();

        table = new Table();
        cueBall = new CueBall();
        cueStick = new CueStick();

        table.rack();
        TG.addChild(table.BG);
        TG.addChild(cueBall.BG);
        setState(State.SCRATCH);

        timer = new Timer(1000 / UPDATE_RATE, new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                tick(1f / (float)UPDATE_RATE);
            }
        });
    }

    /**
     * Creates the Java3D scene graph.
     */
    private static void createScene() {
        System.setProperty("sun.awt.noerasebackground", "true");

        GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D();
        template.setSceneAntialiasing(GraphicsConfigTemplate3D.PREFERRED);
        GraphicsConfiguration config = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getBestConfiguration(template);
        canvas3D = new Canvas3D(config);

        SimpleUniverse simpleU = new SimpleUniverse(canvas3D);
        simpleU.getViewingPlatform().setNominalViewingTransform();
        simpleU.getViewer().getView().setSceneAntialiasingEnable(true);

        scene = new BranchGroup();
        scene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
        scene.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
        scene.setCapability(BranchGroup.ALLOW_CHILDREN_READ);

        TG = new TransformGroup();
        TG.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
        TG.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
        TG.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
        scene.addChild(TG);

        Point3f pos = new Point3f (0.0f, 0.0f, .8f);
        Color3f lightColor = new Color3f (1.0f, 1.0f, 1.0f);
        BoundingSphere bounds =	new BoundingSphere();
        SpotLight light1  = new SpotLight();
        light1.setColor(lightColor);
        light1.setAttenuation(.4f,.4f,.4f);
        light1.setPosition(pos);
        light1.setInfluencingBounds (bounds);
        scene.addChild(light1);

        AmbientLight amb = new AmbientLight(lightColor);
        amb.setInfluencingBounds (bounds);
        scene.addChild(amb);

        simpleU.addBranchGraph(scene);

        // create the frame
        frame = new JFrame("Pool");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.add(canvas3D, BorderLayout.CENTER);
        scoreLabel = new JLabel("Score: 0");
        scoreLabel.setForeground(Color.white);
        scoreLabel.setBackground(Color.black);
        scoreLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
        scoreLabel.setHorizontalAlignment(SwingConstants.CENTER);
        scoreLabel.setFont(scoreLabel.getFont().deriveFont(Font.BOLD));
        frame.add(scoreLabel, BorderLayout.SOUTH);
        frame.setBackground(Color.black);
        canvas3D.setSize(800, 600);
        frame.pack();
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        canvas3D.getView().setProjectionPolicy(View.PARALLEL_PROJECTION);
    }

    public static void shoot(float angle, float power) {
        power *= .025f;
        Vector2f velocity = new Vector2f(-power * (float)Math.sin(-angle), -power * (float)Math.cos(angle));
        cueBall.strike(velocity);
        setState(State.MOTION);
    }

    /**
     * Set the state of the game
     * @param newState New state
     */
     public static void setState(State newState) {
        state = newState;
        switch (state) {
            case SCRATCH:
                cueBall.setState(CueBall.State.SCRATCH);
                cueStick.setState(CueStick.State.HIDDEN);
                break;
            case PLAY:
                cueBall.setState(CueBall.State.NORMAL);
                cueStick.setState(CueStick.State.MOVE);
                break;
            case MOTION:
                cueBall.setState(CueBall.State.NORMAL);
                cueStick.setState(CueStick.State.HIDDEN);
                timer.start();
                break;
        }
    }

    /**
     * Update the world if we're in motion mode.
     * @param delta Time elapsed
     */
    private static void tick (float delta) {
        if (state == State.MOTION) {
            table.tick(delta);
            if (table.areBallsStopped()) {
                timer.stop();
                if (isScratched) {
                    isScratched = false;
                    setState(State.SCRATCH);
                } else {
                    setState(State.PLAY);
                }
            }
        } else {
            timer.stop();
        }
    }

    /**
     * Add a mouse listener to the scene
     * @param ml Mouse listener to add
     * @param mml Mouse Motion listener to add
     */
    public static void addMouseListener(MouseListener ml, MouseMotionListener mml) {
        canvas3D.addMouseListener(ml);
        canvas3D.addMouseMotionListener(mml);
    }

    /**
     * Remove a mouse listener from the scene
     * @param ml Mouse listener to add
     * @param mml Mouse motion listener to add
     */
    public static void removeMouseListener(MouseListener ml, MouseMotionListener mml) {
        canvas3D.removeMouseListener(ml);
        canvas3D.removeMouseMotionListener(mml);
    }

    /**
     * Add a branch group to the scene
     * @param bg Branch group to add
     */
    public static void addToScene(BranchGroup bg) {
        TG.addChild(bg);
    }

}
